package com.wynne.ptldemo.ws;

import akka.actor.ActorRef;
import akka.actor.ActorSelection;
import akka.actor.ActorSystem;
import com.alibaba.fastjson.JSON;
import com.wynne.ptldemo.bo.TagSignalBo;
import com.wynne.ptldemo.service.serviceimpl.MessageDispatcher;
import com.wynne.ptldemo.service.serviceimpl.MockDataService;
import com.wynne.ptldemo.util.SpringContextUtil;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

@ServerEndpoint(value = "/ptl")
@Component
public class StationWebSocket {
    private static final Logger LOGGER = LoggerFactory.getLogger(StationWebSocket.class);

    private String clientInfo;

    private Session session;

    @Autowired
    private ActorSystem actorSystem;

    public static ConcurrentMap<String, List<StationWebSocket>> webSocketMap = new ConcurrentHashMap();

    /**
     * 开启连接
     *
     * @param session
     */
    @OnOpen
    public synchronized void onOpen(final Session session) {
        clientInfo = "1";
        if (StringUtils.isEmpty(clientInfo)) {
            InetSocketAddress addr = WebSocketUtil.getRemoteAddress(session);
            this.clientInfo = addr.toString();
        } else {
            this.clientInfo = clientInfo;
        }
        this.session = session;
        List<StationWebSocket> stationWebSockets = webSocketMap.get(this.clientInfo);
        if (CollectionUtils.isEmpty(stationWebSockets)) {
            List<StationWebSocket> webSockets = new ArrayList<>();
            webSockets.add(this);
            webSocketMap.put(this.clientInfo, webSockets);
        } else {
            stationWebSockets.add(this);
            webSocketMap.put(this.clientInfo, stationWebSockets);
        }
//        IInitActorsService iInitActorsService = (IInitActorsService) SpringContextUtil.getBean("initActorsService");
//        iInitActorsService.initTagActors();
        MockDataService mockDataService = (MockDataService) SpringContextUtil.getBean("mockDataService");
        mockDataService.initControl();
        LOGGER.info("{}加入！当前在线人数为:{}", clientInfo, this.getOnlineCount());
    }

    /**
     * 关闭连接
     */
    @OnClose
    public synchronized void onClose(final Session session) {
        offline(this.clientInfo, session);
        LOGGER.info("有一个连接关闭！当前在线人数为：{}", this.getOnlineCount());
    }

    /**
     * 接收客户端的信息
     *
     * @param message 客户端信息
     */
    @OnMessage
    public void onMessage(final String message, final Session session) {
        try {
            if ("heartBit".equals(message)) {
                this.sendMessage(this.clientInfo, "heartBit");
            } else {
                TagSignalBo signalBo = JSON.parseObject(message, TagSignalBo.class);
                MessageDispatcher messageDispatcher = (MessageDispatcher) SpringContextUtil.getBean("messageDispatcher");
                messageDispatcher.dispatcherMessage(signalBo);
                LOGGER.info("处理来自客户端的消息：", message);
            }
        } catch (Exception e) {
            LOGGER.error("客户信息为{}，处理客户信息时错误：{}", message, e);
        }
    }

    /**
     * 错误信息
     *
     * @param error
     */
    @OnError
    public void onError(final Session session, final Throwable error) {
        LOGGER.error("**发送错误：{}", error.getMessage());
    }

    /**
     * 推送信息给客户端
     *
     * @param clientInfo
     * @param message
     */
    public synchronized void sendMessage(String clientInfo, String message) {
        try {
            List<StationWebSocket> webSockets = webSocketMap.get(clientInfo);
            if (!CollectionUtils.isEmpty(webSockets)) {
                for (StationWebSocket webSocket : webSockets) {
                    webSocket.session.getBasicRemote().sendText(message);
                }
            }
        } catch (IOException e) {
            LOGGER.error("webSocket给客户端发送信息错误：{}", e);
        }
    }

    public static int getOnlineCount() {
        int onlineCount = 0;
        for (Map.Entry<String, List<StationWebSocket>> entry : webSocketMap.entrySet()) {
            onlineCount += entry.getValue().size();
        }
        return onlineCount;
    }

    public synchronized void offline(String clientInfo, Session session) {
        List<StationWebSocket> webSockets = webSocketMap.get(clientInfo);
        Iterator<StationWebSocket> iterator = webSockets.iterator();
        while (iterator.hasNext()) {
            StationWebSocket it = iterator.next();
            if (it.session.equals(session))
                iterator.remove();
        }
    }

}

class WebSocketUtil {
    public static InetSocketAddress getRemoteAddress(Session session) {
        if (session == null) {
            return null;
        }
        RemoteEndpoint.Async async = session.getAsyncRemote();
        //在Tomcat 8.0.x版本有效
//		InetSocketAddress addr = (InetSocketAddress) getFieldInstance(async,"base#sos#socketWrapper#socket#sc#remoteAddress");
        //在Tomcat 8.5以上版本有效
        InetSocketAddress addr = (InetSocketAddress) getFieldInstance(async, "base#socketWrapper#socket#sc#remoteAddress");
        return addr;
    }

    private static Object getFieldInstance(Object obj, String fieldPath) {
        String fields[] = fieldPath.split("#");
        for (String field : fields) {
            obj = getField(obj, obj.getClass(), field);
            if (obj == null) {
                return null;
            }
        }
        return obj;
    }

    private static Object getField(Object obj, Class<?> clazz, String fieldName) {
        for (; clazz != Object.class; clazz = clazz.getSuperclass()) {
            try {
                Field field;
                field = clazz.getDeclaredField(fieldName);
                field.setAccessible(true);
                return field.get(obj);
            } catch (Exception e) {
            }
        }
        return null;
    }
}
